Lambdaから別アカウントのLambdaを呼び出す
こんにちは、虎塚です。
システムのログ収集、監視、アラート通知、アクション実行などにAWS Lambdaを使っていると、LambdaとLambdaをチェーンして使いたいことがあります。さらに、複数のアカウントで発生したイベントを1つのアカウントに集めるようなときには、Lambdaから別のAWSアカウントのLambdaを呼び出したいこともあります。
この記事では、AWS Lambdaから別アカウントのAWS Lambdaを呼びだす方法を説明します。
概要
本記事で設定手順を説明するAWS構成を次の図に示します。
アカウント000000000000のAWS Lambda (caller) が、アカウント111111111111のAWS Lambda (callee) を呼び出します。
動作確認のために、1つ目のAWS Lambdaの前にKinesis Streamを配置しました。また、2つ目のAWS Lambdaは、Lambda関数内の処理で、実行中のEC2インスタンスを停止します。
それぞれのLambdaに必要な権限を次の図に示します。
次の2点がポイントです。
- 呼び出される側のLambdaのリソースポリシーで、アカウント000000000000による操作を許可します。
クロスアカウント呼び出しの許可をリソースポリシーで与えるので、呼び出される側のLambdaの実行ロールで、アカウント000000000000を信頼ポリシーに含める必要はありません。
以降にチュートリアルを用意しましたので、ピンとこない場合は、実際にお試しいただければと思います。
設定手順
Lambdaから別アカウントのLambdaを呼び出す構成を作成します。
- 2つのAWSアカウント000000000000と、111111111111を利用できるものとします。
- Kinesis Stream, EC2, AWS LambdaなどのAWSリソースは、すべて東京リージョンに作成するものとします。
基本的にAWS CLIで作業します。コマンド実行時に、対象のアカウントを間違えないように注意してください。手順では、呼び出し側アカウントに対するコマンドに--profile caller
、呼び出される側アカウントに対するコマンドに--profile callee
をつけます。
1. Kinesis Streamの作成
アカウント0000000000でKinesis Streamを作成します。
次のコマンドで、シャードを1個だけ持つKinesis Stream (my-stream) を作成します。
aws kinesis create-stream --profile caller\ --stream-name my-stream \ --shard-count 1
作成されたことを確認します。
aws kinesis describe-stream --profile caller\ --stream-name my-stream
ストリームのARNは、ステップ2.4で使うので控えておきます。
2. 呼び出し側のLambdaの作成
アカウント0000000000でAWS Lambda (caller) を作成します。
2.1. デプロイパッケージの作成
ローカルで、次のPythonスクリプトをテキストファイルに保存します。
from __future__ import print_function import base64 import boto3 import json import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def create_function_arn(data): region = data['region'] account = data['accountId'] function_name = data['functionName'] return 'arn:aws:lambda:' + region + ':' + account + ':function:' + function_name def lambda_handler(event, context): logger.info(event) for record in event['Records']: data = json.loads(base64.b64decode(record['kinesis']['data'])) function_arn = create_function_arn(data) params = { 'instanceId': data['parameters']['instanceId'] } response = boto3.client('lambda').invoke( ClientContext = 'string', FunctionName = function_arn, InvocationType = 'Event', LogType = 'Tail', Payload = json.dumps(params) ) logger.info(response)
このスクリプトでは、Kinesisからデータを受け取って、呼び出される側のLambda (callee) を特定し、パラメータとしてインスタンスIDを渡して呼び出す処理をします。
デプロイパッケージを作成する環境として、EC2インスタンスを起動します。このインスタンスは、アカウント000000000000とアカウント111111111111のどちらで起動しても構いません。今回は、次のAMIを利用しました。
- Amazon Linux AMI 2016.09.1 (HVM), SSD Volume Type - ami-56d4ad31
ec2-userのホームディレクトリに、Pythonスクリプトをローカルからコピーします。
scp -i ~/.ssh/key.pem ./caller.py ec2-user@XXX.XXX.XXX.XXX:
EC2インスタンスにSSHログインします。
このAMIに初期インストールされているPythonは2.7.12で、pipが含まれているので、pipのインストールを省略できます。
必要なパッケージをインストールします。
sudo yum install python27-devel python27-pip gcc
パッケージ作成の作業環境を整えます。
virtualenv ~/caller source ~/caller/bin/activate
boto3はAWS上の実行環境で利用できるので、ローカルデバッグで利用したいときだけインストールします。
pip install boto3
デプロイパッケージ (caller.zip) に関連ファイルを含めます。関連ファイルがない場合は、スキップします (コマンドを実行してしまっても問題ありません)。
cd $VIRTUAL_ENV/lib/python2.7/site-packages zip -r9 ~/caller.zip * cd $VIRTUAL_ENV/lib64/python2.7/site-packages zip -r9 ~/caller.zip *
デプロイパッケージにスクリプトを含めます。
cd ~ zip -g caller.zip caller.py
EC2インスタンスからログアウトして、作成したデプロイパッケージをローカルにダウンロードしておきます。
scp -i ~/.ssh/key.pem ec2-user@XXX.XXX.XXX.XXX:caller.zip .
このEC2インスタンスは、ステップ3.1でもう一度使うので、起動したままにしておきます。
2.2. Lambdaの実行ロールの作成
まず、IAMロールの信頼ポリシーを、ローカルにファイルで作成します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
次に、上の信頼ポリシーを指定して、IAMロールを作成します。
aws iam create-role --profile caller \ --role-name caller-role \ --assume-role-policy-document file://TrustPolicyForLambda.json
戻り値に含まれるロールのARNは、ステップ2.3で使うので控えておきます。
最後に、作成したIAMロールにアクセスポリシーを追加します。
# Lambdaの実行ログの出力を許可します。一般的なLambdaの実行ロールの権限です。 aws iam attach-role-policy --profile caller \ --role-name caller-role \ --policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute # Kinesis Streamからデータの取得を許可します。 aws iam attach-role-policy --profile caller \ --role-name caller-role \ --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaKinesisExecutionRole # Lambdaの呼び出しを許可します。calleeの呼び出しに必要です。 aws iam attach-role-policy --profile caller \ --role-name caller-role \ --policy-arn arn:aws:iam::aws:policy/service-role/AWSLambdaRole
2.3. デプロイパッケージのアップロード
ステップ2.1で作成したデプロイパッケージのzipファイルを指定して、Lambda関数を作成します。
aws lambda create-function --profile caller \ --function-name caller \ --runtime python2.7 \ --role arn:aws:iam::000000000000:role/caller-role \ --handler caller.lambda_handler \ --zip-file fileb://caller.zip
2.4. イベントソースにKinesis Streamを設定
ステップ1で作成したKinesis Stream (my-stream) を、Lambdaのイベントソースに設定します。これによって、Kinesis Streamにデータが入ると、Lambda (caller) が起動するようになります。
aws lambda create-event-source-mapping --profile caller \ --event-source-arn arn:aws:kinesis:ap-northeast-1:000000000000:stream/my-stream \ --function-name caller \ --starting-position LATEST
3. 呼び出される側のLambdaの作成
アカウント111111111111でAWS Lambda (callee) を作成します。
3.1. デプロイパッケージの作成
ローカルで次のPythonスクリプトをテキストファイルに保存します。
from __future__ import print_function import boto3 import logging logger = logging.getLogger() logger.setLevel(logging.INFO) def lambda_handler(event, context): logger.info(event) instance = event['instanceId'] response = boto3.client('ec2').stop_instances( InstanceIds=[ instance, ] )
ステップ2.1で使ったインスタンスに上のスクリプトをコピーして、前回と同じ手順でデプロイパッケージを作成します。
scp -i ~/.ssh/key.pem ./callee.py ec2-user@XXX.XXX.XXX.XXX:
EC2インスタンスにSSHログインして、パッケージ作成の作業環境を整えます。
virtualenv ~/callee source ~/callee/bin/activate
デプロイパッケージ (callee.zip) に関連ファイルを含めます。
cd $VIRTUAL_ENV/lib/python2.7/site-packages zip -r9 ~/callee.zip * cd $VIRTUAL_ENV/lib64/python2.7/site-packages zip -r9 ~/callee.zip *
デプロイパッケージにスクリプトを含めます。
cd ~ zip -g callee.zip callee.py
EC2インスタンスからログアウトして、作成したデプロイパッケージをローカルにダウンロードしておきます。
scp -i ~/.ssh/key.pem ec2-user@XXX.XXX.XXX.XXX:callee.zip .
このEC2インスタンスは、停止して削除してしまって構いません。
3.2. Lambdaの実行ロールの作成
アカウント111111111111でIAMロールを作成します。
まず、IAMロールの信頼ポリシーを、ローカルにファイルで作成します。
{ "Version": "2012-10-17", "Statement": [ { "Effect": "Allow", "Principal": { "Service": "lambda.amazonaws.com" }, "Action": "sts:AssumeRole" } ] }
ステップ2.2で作成した信頼ポリシーと同じように、Lambdaサービスロールとして必要な設定を定義します。
次に、上の信頼ポリシーを指定して、IAMロールを作成します。
aws iam create-role --profile callee \ --role-name callee-role \ --assume-role-policy-document file://TrustPolicyForLambdaAndCallerAccount.json
戻り値に含まれるロールのARNは、ステップ3.3で使うので控えておきます。
最後に、作成したIAMロールにアクセスポリシーを追加します。
# Lambdaの実行ログの出力を許可します。一般的なLambdaの実行ロールの権限です。 aws iam attach-role-policy --profile callee \ --role-name callee-role \ --policy-arn arn:aws:iam::aws:policy/AWSLambdaExecute # EC2の操作を許可します。今回はEC2を停止させるために追加します。 aws iam attach-role-policy --profile callee \ --role-name callee-role \ --policy-arn arn:aws:iam::aws:policy/AmazonEC2FullAccess
3.3. デプロイパッケージのアップロード
前のステップで作成したデプロイパッケージのzipファイルを指定して、Lambda関数を作成します。
aws lambda create-function --profile callee \ --function-name callee \ --runtime python2.7 \ --role arn:aws:iam::111111111111:role/callee-role \ --handler callee.lambda_handler \ --zip-file fileb://callee.zip
3.4. 権限の追加
アカウント000000000000からこのLambda関数を呼び出せるように、Lambdaのリソースポリシーに権限を追加します。
aws lambda add-permission --profile callee \ --function-name callee \ --statement-id AllowActionFromCaller \ --principal 000000000000 \ --action lambda:InvokeFunction
これで設定が完了しました。
動作確認
Lambda (caller) から、クロスアカウントでLambda (callee) を呼び出せることを確認します。
1. 対象インスタンスの起動確認
Lambda (callee) が停止させるEC2インスタンス (i-12345678901234567) を、アカウント111111111111の東京リージョンに起動します。i-12345678901234567が起動していることを確認します。
aws ec2 describe-instances --profile callee \ --instance-id i-12345678901234567 \ | jq '.Reservations[].Instances[].State.Name' "running"
2. Kinesisへデータ投入
Kinesis Stream (my-stream) に投入するデータを、ローカルにファイルで作成します。
{ "accountId" : "111111111111", "region" : "ap-northeast-1", "functionName" : "callee", "parameters" : { "instanceId" : "i-12345678901234567" } }
上のデータには、呼び出される側のLambdaを特定するための情報 (アカウントID、Lambdaが配置されたリージョン、Lambda関数名) と、処理対象のインスタンスIDが含まれています。
次のコマンドで、Kinesis Streamにデータを投入します。
aws kinesis put-record --profile caller \ --stream-name my-stream \ --data file://sample_event.json \ --partition-key test
3. Lambdaのログ確認
AWS Management Consoleにログインして、CloudWatch LogsでLambdaのログを確認します。
- アカウント000000000000の/aws/lambda/callerロググループ
- アカウント111111111111の/aws/lambda/calleeロググループ
エラーが出力されていないことを確認します。
4. インスタンスの確認
EC2インスタンスが停止されたことを確認します。
aws ec2 describe-instances --profile callee \ --instance-id i-12345678901234567 \ | jq '.Reservations[].Instances[].State.Name' "stopped"
おわりに
LambdaからLambdaをクロスアカウントで呼び出すための設定方法を説明しました。アカウントをまたいで複数のLambdaを繋ぎたいときに、この記事がお役に立てば幸いです。
なお、実際にこういった構成で環境を作る場合は、Lambdaに与える権限を適切に絞りましょう。(たとえば、今回の設定例でいうと、calleeの実行ロールに与える権限は、EC2のフルアクセスよりも停止だけのほうが望ましいです)
それでは、また。